// @enum {string} */
var GameStates = {
  PREGAME: 'pr',
  ROLE: 'ro',
  REVEAL: 're',
  TEAM_SELECTION: 'ts',
  TEAM_VOTE_CLEAR: 'tc',
  TEAM_VOTING: 'tv',
  TEAM_REVEAL: 'tr',
  ON_MISSION: 'om',
  MISSION_REVEAL: 'mr',
  POST_GAME: 'po',
};

// @enum {string} */
var LastMission = {
  NONE: "Nothing",
  FAILED: "Failed",
  PASSED: "Passed",
};

// @enum {string} */
var TeamVote = {
  NONE: "Nothing",
  APPROVE: "Approve",
  REJECT: "Reject"
};

var state_ = null;

var metadata_ = null;

var container_ = null;

var participants_ = null;

var saveValue = null;

var removeValue = null;

var submitDelta = null;

(function(){
  // Prepares a key/value pair for saving
  var prepareForSave = function(keyOrState, opt_value) {
    var state = null;
    if(typeof keyOrState === 'string') {
      state = {};
      state[keyOrState] = opt_value;
    } else if(typeof keyOrState === 'object' && null !== keyOrState) {
      // Ensure that no prototype-level properties are hitching a ride
      state = {};
      for (var key in keyOrState) {
        if(keyOrState.hasOwnPrototype(key)) {
          state[key] = keyOrState[key];
        }
      }
    } else {
      throw 'Unexpected argument.';
    }
    return state;
  };
  
  // Prepares for the removal of a key/val pair
  var prepareForRemove = function(keyOrListToRemove) {
    var delta = null;
    if (typeof keyOrListToRemove === 'string') {
      delta = [keyOrListToRemove];
    } else if(typeof keyOrListToRemove.length === 'number' &&
              keyOrListToRemove.propertyIsEnumerable('length')) {
      // Discard non-string elements.
      for (var i = 0, iLen = keyOrListToRemove.length; i < iLen; ++i) {
        if (typeof keyOrListToRemove[i] === 'string') {
          delta.push(keyOrListToRemove[i]);
        }
      }
    } else {
      throw 'Unexpected argument.';
    }
    return delta;
  };

  // RPC to add and/or remove given values from the shared state
  var submitDeltaInternal = function(addState, opt_removeState) {
    gapi.hangout.data.submitDelta(addState, opt_removeState);
  };
  
  saveValue = function(keyOrState, opt_value) {
    var delta = prepareForSave(keyOrState, opt_value);
    if (delta) {
      submitDeltaInternal(delta);
    }
  };
  
  removeValue = function(keyOrListToRemove) {
    var delta = prepareForRemove(keyOrListToRemove);
    if(delta) {
      submitDeltaInternal({}, delta);
    }
  };
  
  submitDelta = function(addState, opt_removeState) {
    if ((typeof addState !== 'object' && typeof addState !== 'undefined') ||
        (typeof opt_removeState !== 'object' &&
         typeof opt_removeState !== 'undefined')) {
      throw 'Unexpected value for submitDelta';
    }
    var toAdd = addState ? prepareForSave(addState) : {};
    var toRemove = opt_removeState ? prepareForRemove(opt_removeState) :
        undefined;
    submitDeltaInternal(toAdd, toRemove);
  };
})();

function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

// Set current leader
function setMissionLeader(id) {
  saveValue("LEADER", id);
};

// Get current leader
function getMissionLeader() {
  return state_["LEADER"];
};

// Set random mission leader
function setRandomMissionLeader() {
  // For now, just choose the first person in the participants table
  setMissionLeader(participants_[0].id);
};

// Advance mission leader
function advanceMissionLeader() {
  var currentLeaderIndex;
  var currentLeaderID = getMissionLeader();
  var participants = participants_;
  for (var i = 0, iLen = participants.length; i < iLen; ++i) {
    if(participants[i].id == currentLeaderID) {
      currentLeaderIndex = i;
      break;
    }
  }
  if(currentLeaderIndex < participants.length - 1) {
    currentLeaderIndex++;
    setMissionLeader(participants[currentLeaderIndex].id);
  } else {
    setMissionLeader(participants[0].id);  
  }
};

// Get mission leader name
function getMissionLeaderName() {
  if(participants_ == null) { return; }
  var currentLeaderID = getMissionLeader();
  var participants = participants_;
  for (var i = 0, iLen = participants.length; i < iLen; ++i) {
    if(participants[i].id == currentLeaderID) {
      return participants[i].person.displayName;
    }
  }
};

// Set game state
function setGameState(gs) {
  saveValue("GAMESTATE", gs);
};

// Set the Resistance Score
function setResistanceScore(score) {
  saveValue("RSCORE", score);
};

// Set the Spies Score
function setSpyScore(score) {
  saveValue("SSCORE", score);
};

// Get the Resistance Score
function getResistanceScore() {
  return state_["RSCORE"];
};

// Get the Spies Score
function getSpyScore() {
  return state_["SSCORE"];
};

// Set Last Mission State
function setLastMissionState(state) {
  saveValue("LMS", state);
};

// Get Last Mission State
function getLastMissionState() {
  return state_["LMS"];
};

function setTeamVoteByID(id, vote) {
  saveValue("TeamVote:"+id, vote);
};

function getTeamVoteByID(id) {
  return state_["TeamVote:"+id];
};

function drawHasTeamVoted() {
  //var output = $("<div />");
  var output = "";
  //var participants = gapi.hangout.getEnabledParticipants();
  var participants = participants_;
  for (var i = 0, iLen = participants.length; i < iLen; ++i) {
    var vote = getTeamVoteByID(participants[i].id);
    if(vote == undefined) {
      console.error("PLAYER "+participants[i].person.displayName+" doesn't have a vote slot, adding it.");
      setTeamVoteByID(participants[i].id, TeamVote.NONE);
      //output.append(participants[i].person.displayName + ": Waiting...<br />");
      output += participants[i].person.displayName + ": Waiting...<br />";
    } else if(vote == TeamVote.NONE) {
      //output.append(participants[i].person.displayName + ": Waiting...<br />");
      output += participants[i].person.displayName + ": Waiting...<br />";
    } else {
      //output.append(participants[i].person.displayName + ": Voted!<br />");
      output += participants[i].person.displayName + ": Voted!<br />";
    }
  }
  return output;
};

function drawTeamVotes() {
  //var output = $("<div />");
  var output = "";
  //var participants = gapi.hangout.getEnabledParticipants();
  var participants = participants_;
  for (var i = 0, iLen = participants.length; i < iLen; ++i) {
    var vote = getTeamVoteByID(participants[i].id);
    if(vote == undefined) {
      console.error("PLAYER "+participants[i].person.displayName+" doesn't have a vote slot.");
      setTeamVoteByID(participants[i].id, TeamVote.NONE);
      //output.append(participants[i].person.displayName + ": DIDN'T VOTE!!!<br />");
      output += participants[i].person.displayName + ": DIDN'T VOTE!!!<br />";
    } else if(vote == TeamVote.NONE) {
      //output.append(participants[i].person.displayName + ": DIDN'T VOTE!!!<br />");
      output += participants[i].person.displayName + ": DIDN'T VOTE!!!<br />";
    } else if(vote == TeamVote.APPROVE) {
      //output.append(participants[i].person.displayName + ": APPROVE<br />");
      output += participants[i].person.displayName + ": APPROVE<br />";
    } else if(vote == TeamVote.REJECT) {
      //output.append(participants[i].person.displayName + ": REJECT<br />");
      output += participants[i].person.displayName + ": REJECT<br />";
    } else {
      //output.append(participants[i].person.displayName + ": " + vote + "???<br />");
      output += participants[i].person.displayName + ": " + vote + "???<br />";
    }
  }
  return output;
};

function drawMyTeamVote() {
  var vote = getTeamVoteByID(gapi.hangout.getLocalParticipantId());
  if(vote == TeamVote.APPROVE) {
    return "You voted to APPROVE the team<br />";
  } else if(vote == TeamVote.REJECT) {
    return "You voted to REJECT the team<br />";
  } else {
    return "ERROR: Your vote reads as "+vote+". This should't happen<br />";
  }
};

function didTeamVotePass() {
  //var participants = gapi.hangout.getEnabledParticipants();
  var participants = participants_;
  var approves = 0;
  var rejects = 0;
  var validVote = true;
  for (var i = 0, iLen = participants.length; i < iLen; ++i) {
    var vote = getTeamVoteByID(participants[i].id);
    if(vote == undefined || vote == TeamVote.NONE) {
      console.error("Player "+participants[i].person.displayName+" didn't vote for some reason!");
      validVote = false;
    } else if(vote == TeamVote.APPROVE) {
      approves++;
    } else if(vote == TeamVote.REJECT) {
      rejects++;
    } else {
      console.error("Player "+participants[i].person.displayName+" has an unknown team vote: "+vote);
      validVote = false;
    }
  }
  if(validVote == false) {
    console.error("INVALID TEAM VOTE");
    return false;
  } else {
    return approves > rejects;
  }
};

// Clear team votes
function clearTeamVotes() {
  //var participants = gapi.hangout.getEnabledParticipants();
  var participants = participants_;
  if(participants == null) {
    return;
  }
  for (var i = 0, iLen = participants.length; i < iLen; ++i) {
    if(getTeamVoteByID(participants[i].id) != TeamVote.NONE) {
      setTeamVoteByID(participants[i].id, TeamVote.NONE);
    }
  }
};

function areTeamVotesClear() {
  //var participants = gapi.hangout.getEnabledParticipants();
  var participants = participants_;
  for (var i = 0, iLen = participants .length; i < iLen; ++i) {
    var vote = getTeamVoteByID(participants[i].id);
    if(vote == undefined) {
      console.error("PLAYER "+participants[i].person.displayName+" doesn't have a vote slot, adding and bailing.");
      setTeamVoteByID(participants[i].id, TeamVote.NONE);
      return false;
    } else if(vote != TeamVote.NONE) {
      return false;
    }
  }
  return true;
};

function hasEveryoneTeamVoted() {
  //var participants = gapi.hangout.getEnabledParticipants();
  var participants = participants_;
  for (var i = 0, iLen = participants .length; i < iLen; ++i) {
    var vote = getTeamVoteByID(participants[i].id); 
    if(vote == undefined) {
      console.error("PLAYER "+participants[i].person.displayName+" doesn't have a vote slot, adding and bailing.");
      setTeamVoteByID(participants[i].id, TeamVote.NONE);
      return false;
    } else if(vote == TeamVote.NONE) {
      return false;
    }
  }
  return true;
};

function hasVotedForTeam(id) {
  var vote = getTeamVoteByID(id);
  if(vote == undefined) {
    console.error("PLAYER WITH ID "+id+" doensn't have a vote slot, adding and bailing.");
    setTeamVoteByID(id, TeamVote.NONE);
    return false;
  } else if(vote == TeamVote.NONE) {
    return false;
  }
  return true;
};

function doTeamVote(vote) {
  setTeamVoteByID(gapi.hangout.getLocalParticipantId(), vote);
};

function changeTeamVote() {
  var vote = getTeamVoteByID(gapi.hangout.getLocalParticipantId());
  if(vote == TeamVote.APPROVE) {
    doTeamVote(TeamVote.REJECT);
  } else if (vote == TeamVote.REJECT) {
    doTeamVote(TeamVote.APPROVE);
  } else {
    console.error("PLAYER WITH ID "+gapi.hangout.getLocalParticipantId()+" is trying to change their vote, but they haven't voted yet!");
  }
};

function haveITeamVoted() {
  var vote = getTeamVoteByID(gapi.hangout.getLocalParticipantId());
  if(vote == undefined) {
    console.error("PLAYER WITH ID "+id+" doesn't have a vote slot, adding and bailing.");
    setTeamVoteByID(gapi.hangout.getLocalParticipantId(), TeamVote.NONE);
    return false;
  } else if(vote == TeamVote.NONE) {
    return false;
  }
  return true;
}

// Create required DOM elements and listeners.
function prepareAppDOM() {
  console.log("Prepareing App DOM");
  container_ = $('<div />');
  var body = $('body');
  body.mousedown(function(e) {
  }).append(container_);
}

// Return the html for a Game State Change button
function createGameStateChangeButton(a_gs, a_text) {
  var onButtonMouseDown = function() {
    $(this).addClass('selected');
  };
  var getButtonMouseUpHandler = function(gs) {
    return function() {
      $(this).removeClass('selected');
      setGameState(gs);
    };
  };

  var link = $('<a />')
      .attr('href', '#')
      .text(a_text)
      .click(function() {
        return false;
      });
  var button = $('<div />')
      .addClass('button')
      .append(link)
      .mousedown(onButtonMouseDown)
      .mouseup(getButtonMouseUpHandler(a_gs));
      
  return button;
}

// Return true if you are the leader
function amLeader() {
  return state_["GMID"] == gapi.hangout.getLocalParticipantId();
}

// Returns true if there is a leader
function isLeaderSet() {
  return state_["GMID"] != "NULL";
}

// Become the game leader
function becomeLeader() {
  saveValue("GMID", gapi.hangout.getLocalParticipantId());
}

// Return the html for a button with user provided mouse down and mouse up handlers
function createButton(a_text, a_mouseDown, a_mouseUp) {
  var link = $('<a />')
    .attr('href', '#')
    .text(a_text)
    .click(function() {
        return false;
      });
      
  var button = $('<div />')
      .addClass('button')
      .append(link)
      .mousedown(a_mouseDown)
      .mouseup(a_mouseUp);

  return button;
}

// Return the html for a button that will make you the leader
function createTakeLeaderButton() {
  var onButtonMouseDown = function() {
    $(this).addClass('selected');
  };
  var getButtonMouseUpHandler = function() {
    return function() {
      $(this).removeClass('selected');
      becomeLeader();
    };
  };
  
  return createButton("Become the Game Master", onButtonMouseDown, getButtonMouseUpHandler());
}

// Draws the result of the last mission
function drawLastMissionResults() {
  if(getLastMissionState() == LastMission.PASSED) {
    var results = $('<div />')
      .text("PASSED!");
    return results;
  } else if(getLastMissionState() == LastMission.FAILED) {
    var results = $('<div />')
      .text("FAILED!");
    return results;    
  }
}

// Draws the current score
function drawCurrentScore() {
  var output = "Resistance: ".concat(getResistanceScore(), " Spies: ", getSpyScore());
  return output;
}

// Pre Game Render
function renderPregame() {
  var output = $('<div />');
  output.append("PREGAME STATE");
  if(amLeader()) {
    output.append(createGameStateChangeButton(GameStates.ROLE, "Assign Roles"));
  }
  return output;
}

// Role View Render
function renderRoleView() {
  participants_ = gapi.hangout.getEnabledParticipants();
  if(amLeader()) {
    setRandomMissionLeader();
  }

  var output = $('<div />');
  output.append("ROLE VIEW STATE");
  if(amLeader()) {
    output.append(createGameStateChangeButton(GameStates.REVEAL, "Reveal Phase"));
  }
  return output;
}

// Reveal state render
function renderReveal() {
  var output = $('<div />');
  output.append("SPY REVEAL STATE");
  if(amLeader()) {
    output.append(createGameStateChangeButton(GameStates.TEAM_SELECTION, "End Reveal Phase"));
  }
  return output;
}

// Team Selection render
function renderTeamSelection() {
  clearTeamVotes();
  var output = $('<div />');
  output.append("TEAM SELECTION STATE<br />");
  output.append(drawCurrentScore());
  output.append("<br />");
  output.append("Leader: " + getMissionLeaderName() + "<br />");
  if(amLeader()) {
    output.append(createGameStateChangeButton(GameStates.TEAM_VOTING, "Finalize Team Selection"));
  }
  return output;
}

// Team Vote Clear render
function renderTeamVoteClear() {
  //if(areTeamVotesClear()) {
  //  setGameState(GameStates.TEAM_VOTING);
  //} else {
  //  clearTeamVotes();
  //}
  //var output = $('<div />');
  //output.append("CLEARING TEAM VOTES. PLEASE WAIT.");
  //return output;
  
  clearTeamVotes();
  setGameState(GameStates.TEAM_VOTING);
  return "";
}

// Team Voting render
function renderTeamVoting() {
  var output = $('<div />');
  output.append("TEAM VOTING STATE<br />");
  output.append(drawCurrentScore());
  output.append("<br />");
  output.append("Leader: " + getMissionLeaderName() + "<br />");
  output.append(drawHasTeamVoted());
  output.append("<br />");
  if(haveITeamVoted() == false) {
    // Draw voting boxes
    var onButtonMouseDown = function() {
      $(this).addClass('selected');
    };
    var getButtonMouseUpHandlerApprove = function() {
      $(this).removeClass('selected');
      doTeamVote(TeamVote.APPROVE);
    };
    var getButtonMouseUpHandlerReject = function() {
      $(this).removeClass('selected');
      doTeamVote(TeamVote.REJECT);
    };
    output.append(createButton("Approve", onButtonMouseDown, getButtonMouseUpHandlerApprove));
    output.append(createButton("Reject", onButtonMouseDown, getButtonMouseUpHandlerReject));
  } else { 
    output.append(drawMyTeamVote());
    var onButtonMouseDown = function() {
      $(this).addClass('selected');
    };
    var getButtonMouseUpHandlerChange = function() {
      $(this).removeClass('selected');
      changeTeamVote();
    };
    output.append(createButton("Change Vote", onButtonMouseDown, getButtonMouseUpHandlerChange));
  }
  if(amLeader()) {
    if(hasEveryoneTeamVoted()) {
      output.append("<br />");
      output.append(createGameStateChangeButton(GameStates.TEAM_REVEAL, "Reveal Votes"));
    }
  }
  return output;
}

// Team Reveal render()
function renderTeamReveal() {
  var output = $('<div />');
  output.append(drawTeamVotes());
  output.append("<br />");
  output.append("Leader: " + getMissionLeaderName() + "<br />");
  var onButtonMouseDown = function() {
    $(this).addClass('selected');
  };
  var getButtonMouseUpHandlerVoteFailed = function() {
    $(this).removeClass('selected');
    advanceMissionLeader();
    setGameState(GameStates.TEAM_SELECTION);
  };
  
  if(didTeamVotePass()) {
    output.append("Vote Passed!");
    if(amLeader()) {
      output.append(createGameStateChangeButton(GameStates.ON_MISSION, "Go On Mission"));
    }
  } else {
    output.append("Vote Failed!");
    if(amLeader()) {
      //output.append(createGameStateChangeButton(GameStates.TEAM_SELECTION, "Pick a New Team"));
      output.append(createButton("Pick a New Team", onButtonMouseDown, getButtonMouseUpHandlerVoteFailed));
    }
  }
  return output;
}

// On Mission Render
function renderOnMission() {
  //return createGameStateChangeButton(GameStates.MISSION_REVEAL, "Complete Mission")
  var onButtonMouseDown = function() {
    $(this).addClass('selected');
  };
  var getButtonMouseUpHandlerPass = function() {
      $(this).removeClass('selected');
        setLastMissionState(LastMission.PASSED);
        setResistanceScore((parseInt(getResistanceScore()) + 1).toString());
        setGameState(GameStates.MISSION_REVEAL);
  };
  var getButtonMouseUpHandlerFail = function() {
      $(this).removeClass('selected');
        setLastMissionState(LastMission.FAILED);
        setSpyScore((parseInt(getSpyScore()) + 1).toString());
        setGameState(GameStates.MISSION_REVEAL);
  };  
  
  var output = $('<div />');
  output.append("ON MISSION STATE<br />");
  output.append(drawCurrentScore());
  output.append("<br />");
  output.append("Leader: " + getMissionLeaderName() + "<br />");
  if(amLeader()) {
    output.append(createButton("Pass Mission", onButtonMouseDown, getButtonMouseUpHandlerPass));
    output.append(createButton("Fail Mission", onButtonMouseDown, getButtonMouseUpHandlerFail));
  }
  return output;
}

// Mission Reveal Render
function renderMissionReveal() {
  var output = $('<div />');
  output.append("MISSION RESULTS STATE<br />");
  output.append(drawCurrentScore());
  output.append("<br />");
  output.append("Leader: " + getMissionLeaderName() + "<br />");
  output.append(drawLastMissionResults());
  
  var onButtonMouseDown = function() {
    $(this).addClass('selected');
  };
  var getButtonMouseUpHandlerNextMission = function() {
    $(this).removeClass('selected');
    advanceMissionLeader();
    setGameState(GameStates.TEAM_SELECTION);
  };
  
  if(amLeader()) {
    if(parseInt(getSpyScore()) < 3 && parseInt(getResistanceScore()) < 3) {
      output.append(createButton("Continue Game", onButtonMouseDown, getButtonMouseUpHandlerNextMission));
      //output.append(createGameStateChangeButton(GameStates.TEAM_SELECTION, "Continue Game"));
    } else {
      output.append(createGameStateChangeButton(GameStates.POST_GAME, "Finish Game"));
    }
  }
  return output;
}

// Post Game Render
function renderPostGame() {
  var output = $('<div />');
  output.append("POST GAME STATE<br />");
  output.append(drawCurrentScore());
  if(amLeader()) {
    output.append(createGameStateChangeButton(GameStates.PREGAME, "New Game"));
  };
  return output;
}

// Render the app
function render() {
  // DEBUG : Check out the participants
  console.log(gapi.hangout.getEnabledParticipants());

  if(!state_ || !metadata_) {
    return;
  }
  
  switch(state_["GAMESTATE"])
  {
    case 'pr':
      container_
            .empty()
            .append(renderPregame());
      break;
    case 'ro':
      container_
            .empty()
            .append(renderRoleView());
      break;
    case 're':
      container_
            .empty()
            .append(renderReveal());  
      break;
    case 'ts':
      container_
            .empty()
            .append(renderTeamSelection());
      break;
    case 'tv': 
      container_
            .empty()
            .append(renderTeamVoting());
      break;
    case 'tr':
      container_
            .empty()
            .append(renderTeamReveal());
      break;
    case 'om':
      container_
            .empty()
            .append(renderOnMission());
      break;
    case 'mr':
      container_
            .empty()
            .append(renderMissionReveal());
      break;
    case 'po':
      container_
            .empty()
            .append(renderPostGame());
      break;
    default:
      container_
            .empty();
  }
  
  if(!isLeaderSet()) {
    container_.append(createTakeLeaderButton());
  }
}

// Syncs local copies of shared state with those on the server
function updateLocalDataState(state, metadata) {
  console.log("Data updated...");
  state_ = state;
  metadata_ = metadata;
  console.log(state);
  render();
}

(function() {
  if(gapi && gapi.hangout) 
  {
    var initHangout = function(apiInitEvent) 
    {
      if (apiInitEvent.isApiReady) {
        console.log("Initializing hangout...");
        prepareAppDOM();
      
        gapi.hangout.data.onStateChanged.add(function(stateChangeEvent) {
          updateLocalDataState(stateChangeEvent.state,
                               stateChangeEvent.metadata);
        });
      
        if (!state_) {
          var state = gapi.hangout.data.getState();
          var metadata = gapi.hangout.data.getStateMetadata();
          if(state && metadata) {
            updateLocalDataState(state, metadata);
          }
        }
      
        setGameState(GameStates.PREGAME);
        setResistanceScore("0");
        setSpyScore("0");
        saveValue("GMID", "NULL");
        setLastMissionState(LastMission.NONE);
        participants_ = gapi.hangout.getEnabledParticipants();
        clearTeamVotes();
      
        gapi.hangout.onApiReady.remove(initHangout);
      } else {
        console.log("API isn't ready");
      }
    }
    
    gapi.hangout.onApiReady.add(initHangout);
  }
  else
  {
    console.log("gapi and gapi.hangout not available");
  }
})();